Settings within the linker, ld(1), control the layout of text and data sections within a program. Occasionally it is desirable to override the default layout of these sections. Prior to the 6.0.2 compiler release, ld provided several command line options (-T, -D, -B, and several options beginning with -X) to override the default layout. Because these options can be difficult to use, the 6.0.2 linker introduces a more flexible solution for controlling program layout: the ELF Layout Specification language, ELSPEC.
The elspec(5) manual page contains a complete ELSPEC language definition as well as sample ELSPEC files. This article briefly describes how programmers can use ELSPEC. It provides examples in both C and FORTRAN.
Embedded systems and multi-process applications typically require special layout of text and data. Multi-process applications often require that some data be local to each thread. Embedded systems require data and instructions to reside within certain address ranges and typically have very specific alignment requirements. All of these needs are now addressed through the common interface of ELSPEC.
Examples
The remainder of this article provides examples in both C and FORTRAN. To see a sample ELSPEC file for a particular application, add the options -Wl,-elsmap to the compile line, or add the option -elsmap to the linker line.
Specifying the Virtual Address of a FORTRAN Common Block
This example illustrates how to place a FORTRAN common block at virtual address 0x50040000. To see the default addresses that the linker chose for the variables foo1 and foo2 in the common block aa, compile and execute the program as shown below:
% f77 addr1.f -o addr1
% ./addr1
100120D0
100130D0
To change the addresses of foo1 and foo2, include the ELSPEC file eladdr1 on the compile line, and rerun the program.
% f77 addr1.f -o addr1 -Wl,-elspec,eladdr1
% ./addr1
50040000
50041000
c file addr1.f program addr1 integer foo1(1024) integer foo2(1024) common /aa/ foo1,foo2 c address of 1st common aa entry write(6,10)%loc(foo1) c address of 2nd common aa entry write(6,10)%loc(foo2) 10 format(x,z) stop end # start file eladdr1 beginseg name text segtype LOAD segflags R X segalign 0x1000 contents default endseg beginseg name data segtype LOAD segflags R W segalign 0x1000 contents default endseg # create an additional data segment for the # section .mybss0. Set the address for the # common block aa. beginseg segtype LOAD segflags R W vaddr 0x50040000 contents beginscn .mybss0 scntype NOBITS scnflags ALLOC WRITE sym aa_ endscn endseg # end file eladdr1Specifying the Virtual Address for the Text and Data Sections
This example illustrates how to determine the default addresses for text and data sections and how to specify new addresses. The default virtual addresses of the text and data sections under the 6.0.2 compilers are different from those of the compilers available with IRIX 4.0.x and IRIX 5.x. To display the default virtual addresses, compile and execute the example C program as shown below:
% cc -DBUG compact1.c -o compact1
% ./compact1
Start of program text = 0x10000c50
Start of program data = 0x10011000
To change the virtual addresses, compile the program using the ELSPEC file elcompact1 and execute it. Note that there is a bug in the 6.0.2 C compiler that causes the variable _ftext to be undefined when using an ELSPEC file. Because of this bug, the example displays the virtual address of the entry point symbol __start, which is close to the text origin. Most programs should not encounter this bug. However, in those cases where this bug occurs, ELSPEC cannot be used to override the default virtual addresses of the text section. This bug is expected to be resolved in a future release of the linker.
% cc -DBUG compact1.c -o compact1 -Wl,-elspec, elcompact1
% ./compact1
Start of program text = 0x4001f0
Start of program data = 0x10000000
/* file compact1.c */ #include <stdio.h> #include <stdlib.h> #ifdef BUG extern int __start[]; #else extern int _ftext[]; #endif extern int _fdata[]; #define SIZE 4096 int array[SIZE]; void main(void) { #ifdef BUG printf("Start of program text = 0x%lx\n",(unsigned long)__start); #else printf("Start of program text = 0x%lx\n",(unsigned long)_ftext); #endif printf("Start of program data = 0x%lx\n",(unsigned long)_fdata); exit(0); } # start file elcompact1 beginseg name text # set text's origin segtype LOAD vaddr 0x400000 segflags R X segalign 0x1000 contents default endseg beginseg name data segtype LOAD # set data's origin vaddr 0x10000000 segflags R W X segalign 0x1000 contents default endseg beginseg name noload segtype noload contents default endseg # end file elcompact1Multiple Copies of Data Structures
Multi-process applications often need each process to have a separate copy of some data structures. To illustrate the issue of multiple processes not maintaining their own data structures, compile and execute the program sproc1 as shown below. The intent of sproc1 is to sproc(2) three processes and have each process fill its own array with a different number. Thus, the first process (process 4691) should fill its entire array with zeros. The second process (process 4692) should write ones to its copy of the array, while the third process (process 4693) should fill its copy with the number two.
% cc sproc1.c -o sproc1 % ./sproc1
pid 4692, array[0] = 1
pid 4691, array[0] = 0
pid 4692, array[1] = 0
pid 4691, array[1] = 0
pid 4692, array[2] = 0
pid 4691, array[2] = 0
pid 4693, array[0] = 2
pid 4693, array[1] = 2
pid 4693, array[2] = 2
However, because all three processes read and write to the exact same array foo1, incorrect results are produced.
The ELSPEC file elsproc1 listed below can be used to circumvent this behavior. It forces each process to create its own local copy of the array foo1. Compile and execute the sample program sproc1 as shown below. Note that each process (4709, 4710, and 4711) now correctly writes the values 0, 1, or 2 to all three elements of their copy of the array foo1.
% cc sproc1.c -o sproc1 -common -Wl,-elspec, elsproc1
% ./sproc1
pid 4709, array[0] = 0
pid 4710, array[0] = 1
pid 4709, array[1] = 0
pid 4710, array[1] = 1
pid 4709, array[2] = 0
pid 4710, array[2] = 1
pid 4711, array[0] = 2
pid 4711, array[1] = 2
pid 4711, array[2] = 2
/* file sproc1.c */ #include <sys/types.h> #include <unistd.h> #include <sys/prctl.h> #include <stdio.h> #include <stdlib.h> #include <signal.h> #define SIZE 3 int foo1[SIZE]; volatile int waiting = 1; void r1(void *val) { int i,loc=*(int*)val; pid_t pid = getpid(); while(waiting); for(i=0;i<SIZE;i++) foo1[i] = loc; for(i=0;i<SIZE;i++) { printf("pid %d, foo1[%d] = %d\n",pid,i,foo1[i]); } } void main(void) { int i; int d[SIZE] = {0,1,2}; for(i=0;i<SIZE;i++) { if (-1==sproc(r1,PR_SALL,(void*)&d[i])) { printf("sproc failed\n"); if (kill(0,9)==-1) printf("kill failed\n"); } } sginap(50); waiting = 0; sginap(200); exit(0); } # start file elsproc1 beginseg segtype LOAD segflags R X vaddr 0x10000000 segalign 0x1000 contents default endseg beginseg segtype LOAD segflags R W segalign 0x1000 contents default endseg beginseg segtype LOAD # The use of L in segflags field is key. This # allows each process to get a separate copy of # the array foo1. segflags R W L contents beginscn .mybss scntype NOBITS scnflags ALLOC WRITE sym foo1 endscn endseg # end file elsproc1